热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

主存|正确性_Java并发编程深入分析AtomicInteger

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java并发编程深入分析AtomicInteger相关的知识,希望对你有一定的参考价值。什么是线程安全性

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java并发编程深入分析AtomicInteger相关的知识,希望对你有一定的参考价值。


什么是线程安全性
如果一个类可以安全地被多个线程使用&#xff0c;它就是线程安全的。你无法对此论述提出任何争议&#xff0c;但也无法从中得到更多有意义的帮助。那么我们如何辨别线程安全与非线程安全的类&#xff1f;我们甚至又该如何理解“安全”呢&#xff1f; 任何一个合理的“线程安全性”定义&#xff0c;其关键在于“正确性”的概念。在<>书中作者是这样定义的&#xff1a;一个类是是线程安全的&#xff0c;是指在被多个线程访问时&#xff0c;类可以持续进行正确的行为。
提示&#xff1a;书中作者还写到&#xff0c;如下&#xff1a;
当多个线程访问一个类时&#xff0c;如果不用考虑这些线程在运行时环境下的调度和交替执行&#xff0c;并且不需要额外的同步及在调用方代码不必作其他的协调&#xff0c;这个类的行为仍然是正确的&#xff0c;那么称这个类是线程安全的。
代码演示&#xff1a;实现一个计数器
[java] view plain copy 在CODE上查看代码片派生到我的代码片
package com.lll.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class UnsafeCount
//public static AtomicInteger count &#61; new AtomicInteger(0);
public static int count &#61; 0;

public static void inc()
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();

//count.incrementAndGet();
count&#43;&#43;;

public static void main(String[] args) throws InterruptedException
ExecutorService service&#61;Executors.newFixedThreadPool(Integer.MAX_VALUE);
for (int i &#61; 0; i <100; i&#43;&#43;)
service.execute(new Runnable()
&#64;Override
public void run()
UnsafeCount.inc();

);

service.shutdown();
//避免出现main主线程先跑完而子线程还没结束&#xff0c;在这里给予一个关闭时间
service.awaitTermination(3000,TimeUnit.SECONDS);
System.out.println("运行结果:UnsafeCount.count&#61;" &#43; UnsafeCount.count);

提示:为了提高测试的准确性&#xff0c;注意查看第三十六行注释“避免出现main主线程先跑完而子线程还没结束&#xff0c;在这里给予一个关闭时间”。
运行结果&#xff1a;
运行结果:UnsafeCount.count&#61;92
运行结果:UnsafeCount.count&#61;98
….
根据结果可以看出在多线程环境下&#xff0c;没有获得期望的正确结果100.

原因分析
大家都知道&#xff0c;计算机在执行程序时&#xff0c;每条指令都是在CPU中执行的&#xff0c;而执行指令过程中&#xff0c;势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主内存中的&#xff0c;这时就存在一个问题&#xff0c;由于CPU执行速度很快&#xff0c;而从主内存读取数据和向主内存写入数据的过程跟CPU执行指令的速度比起来要慢的多&#xff0c;因此如果任何时候对数据的操作都要通过和主内存的交互来进行&#xff0c;会大大降低指令执行的速度。因此在就有了主内存和本地内存。也就是&#xff0c;当程序在运行过程中&#xff0c;会将运算需要的数据从主存复制一份到线程的本地内存中&#xff0c;创建一个count变量副本&#xff0c;那么CPU进行计算时就可以直接从本地内存加载&#xff0c;使用&#xff0c;赋值和写入到内存中&#xff0c;最后再将副本值同步到主内存中。如下图&#xff1a;

图片摘自http://images.cnblogs.com/cnblogs_com/aigongsi/201204/201204011757235219.jpg
然而这些操作是不具备原子性的&#xff0c;当线程一正在将新的副本值同步到主内存之前&#xff0c;线程二进来了且从本地内存加载到的是线程一修改前的旧数据&#xff0c;所以导致结果数据和预期的不一样。

解决办法一
使用AtomicInteger
一个提供原子操作的Integer的类。在Java语言中&#xff0c;&#43;&#43;i和i&#43;&#43;操作并不是线程安全的&#xff0c;在使用的时候&#xff0c;不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。代码如下&#xff1a;
[java] view plain copy 在CODE上查看代码片派生到我的代码片
package com.lll.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class UnsafeCount
public static AtomicInteger count &#61; new AtomicInteger(0);
//public static int count &#61; 0;

public static void inc()
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();

count.incrementAndGet();
//count&#43;&#43;;

public static void main(String[] args) throws InterruptedException
ExecutorService service&#61;Executors.newFixedThreadPool(Integer.MAX_VALUE);
for (int i &#61; 0; i <100; i&#43;&#43;)
service.execute(new Runnable()
&#64;Override
public void run()
UnsafeCount.inc();

);

service.shutdown();
//避免出现main主线程先跑完而子线程还没结束&#xff0c;在这里给予一个关闭时间
service.awaitTermination(3000,TimeUnit.SECONDS);
System.out.println("运行结果:UnsafeCount.count&#61;" &#43; UnsafeCount.count);

测试结果
运行结果:UnsafeCount.count&#61;100
AtomicInteger源码剖析

[java] view plain copy 在CODE上查看代码片派生到我的代码片
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe &#61; Unsafe.getUnsafe();
private static final long valueOffset;

static
try
valueOffset &#61; unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
catch (Exception ex) throw new Error(ex);

从上面的UML类图和代码可以看出AtomicInteger和Unsafe存在关联关系。它在类中获取了Unsafe的实例&#xff0c;然后通过Unsafe实例的objectFieldOffset方法获得value在内存中的位置。为什么它要获取value在内存中的位置&#xff1f;我们从incrementAndGet方法依次往下分析
[java] view plain copy 在CODE上查看代码片派生到我的代码片
/**
* Atomically increments by one the current value.
*
* &#64;return the updated value
*/
public final int incrementAndGet()
for (;;)
int current &#61; get();
int next &#61; current &#43; 1;
if (compareAndSet(current, next))
return next;


第一步&#xff1a;通过get()方法获得value的值&#43;1&#xff0c;然后传给了compareAndSet方法。compareAndSet代码如下&#xff1a;
[java] view plain copy 在CODE上查看代码片派生到我的代码片
public final boolean compareAndSet(int expect, int update)
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

&#43;——————————————————————&#43;
“ Atomically sets the value to the given updated value if the current value &#64;code &#61;&#61; the expected value.&#64;return true if successful. False return indicates that the actual value was not equal to the expected value.”
&#43;——————————————————————&#43;
第二步&#xff1a;compareAndSet()方法拿到原值和要更新的值。通过代码上的注释&#xff0c;我们可以知道如果valueOffset位置包含的值与expect值相同&#xff0c;则更新valueOffset位置的值为update&#xff0c;并返回true&#xff0c;否则不更新&#xff0c;返回false。


推荐阅读
  • CEPH LIO iSCSI Gateway及其使用参考文档
    本文介绍了CEPH LIO iSCSI Gateway以及使用该网关的参考文档,包括Ceph Block Device、CEPH ISCSI GATEWAY、USING AN ISCSI GATEWAY等。同时提供了多个参考链接,详细介绍了CEPH LIO iSCSI Gateway的配置和使用方法。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • Java自带的观察者模式及实现方法详解
    本文介绍了Java自带的观察者模式,包括Observer和Observable对象的定义和使用方法。通过添加观察者和设置内部标志位,当被观察者中的事件发生变化时,通知观察者对象并执行相应的操作。实现观察者模式非常简单,只需继承Observable类和实现Observer接口即可。详情请参考Java官方api文档。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
author-avatar
tlgcc
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有